﻿//	Copyright (c) 2012 Jonathan Loe
//
//	MIT license (http://en.wikipedia.org/wiki/MIT_License)
//
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights 
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//	of the Software, and to permit persons to whom the Software is furnished to do so, 
//	subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in all 
//	copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//	PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//	FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Text;
using System.Transactions;

using Ordinaire.Db;
using Ordinaire.Security;
using Ordinaire.Security.Data;

namespace Ordinaire
{
    /// <summary>
    /// Represents a system that faciliates initial setup.
    /// </summary>
    public class Application : DataManager
    {
        #region Variables

        /// <summary>
        /// The default userid used as the creator's userid when inserting data into database.
        /// </summary>
        public const string DefaultUserId = "system";

        /// <summary>
        /// Preferred authoriser to create initial roles.
        /// </summary>
        private IAuthorisation authoriser;

        /// <summary>
        /// Preferred authenticator to create administrator.
        /// </summary>
        private IAuthentication authenticator;

        #endregion

        #region Constructor

        /// <summary>
        /// Returns new instance of Ordinaire.Application.
        /// </summary>
        /// <param name="authenticator">the preferred authenticator</param>
        /// <param name="authoriser">the preferred authoriser</param>
        public Application(IAuthentication authenticator, IAuthorisation authoriser)
        {
            this.authenticator = authenticator;
            this.authoriser    = authoriser;
        }

        #endregion

        #region Property methods

        /// <summary>
        /// Gets or sets the default authenticator.
        /// </summary>
        public IAuthentication Authenticator
        {
            get { return authenticator; }
            set { authenticator = value; }
        }

        /// <summary>
        /// Gets or set the default authoriser.
        /// </summary>
        public IAuthorisation Authoriser
        {
            get { return authoriser; }
            set { authoriser = value; }
        }

        #endregion

        /// <summary>
        /// Setup administrator account with the specified userid, password, and rolename.
        /// </summary>
        /// <param name="userid">userid or username to use</param>
        /// <param name="password">password to set</param>
        /// <param name="roleName">the administrator role name</param>
        /// <returns>true if successful, else false</returns>
        public bool SetupAdmin(string userid, string password, string roleName)
        {
            if (String.IsNullOrEmpty(userid))
            {
                throw new ArgumentNullException("userid");
            }
            if (String.IsNullOrEmpty(password))
            {
                throw new ArgumentNullException("password");
            }
            if (String.IsNullOrEmpty(roleName))
            {
                throw new ArgumentNullException("roleName");
            }

            List<Role> roles = SetupRoles(new string[] { roleName });

            List<KeyValuePair<string, string>> userPasswords = new List<KeyValuePair<string, string>>();
            userPasswords.Add(new KeyValuePair<string, string>(userid, password));
            List<UserData> users = SetupUsers(userPasswords);

            using (OContext context = Context)
            {
                var getRoleId = from r in roles
                                where r.Name == roleName
                                select r.Id;
                int roleId = getRoleId.Single();

                string truncate = String.Format("truncate table [{0}]", (new Ordinaire.Security.Data.UserRole()).AttributeName<TableAttribute>());
                context.ExecuteCommand(truncate);

                // Associate the user with the admin role
                UserRole userRole  = new UserRole();
                userRole.UserId    = userid;
                userRole.RoleId    = roleId;
                userRole.CreatedBy = DefaultUserId;

                context.UserRoles.InsertOnSubmit(userRole);
                context.SubmitChanges();

                return true;
            }
        }

        /// <summary>
        /// Setup specified roles by inserting them into database.
        /// </summary>
        /// <param name="names">list of role names to create</param>
        /// <returns>list of roles if successful, else an empty list</returns>
        /// <remarks>it is assumed that the roles specified do not exist</remarks>
        public List<Role> SetupRoles(string[] names)
        {
            if (authoriser == null)
            {
                throw new ArgumentNullException("authoriser");
            }

            return authoriser.Setup(names, DefaultUserId);
        }

        /// <summary>
        /// Setup specified users by populating their details into database.
        /// </summary>
        /// <param name="userPasswords">list of user and password pairs</param>
        /// <returns>list of users that are successfully populated</returns>
        public List<UserData> SetupUsers(List<KeyValuePair<string, string>> userPasswords)
        {
            List<UserData> users = new List<UserData>();

            if (userPasswords == null || userPasswords.Count == 0)
            {
                return users;
            }

            using (OContext context = Context)
            {
                using (TransactionScope transaction = new TransactionScope())
                {
                    string truncate = String.Format("truncate table [{0}]", (new UserData()).AttributeName<TableAttribute>());
                    context.ExecuteCommand(truncate);

                    foreach (KeyValuePair<string, string> userPassword in userPasswords)
                    {
                        string userid   = userPassword.Key;
                        string password = userPassword.Value;

                        UserData user = new UserData();
                        user.Id        = userid;
                        user.Password  = Cryptographer.Hash(password);
                        user.Name      = userid.CapitaliseFirstLetter();
                        user.CreatedBy = DefaultUserId;
                        users.Add(user);

                        context.Users.InsertOnSubmit(user);
                    }

                    context.SubmitChanges();
                    transaction.Complete();
                    return users;
                }
            }
        }
    }
}
